/*-------------------------------------------------------------------------
 *
 * copyto.c
 *		COPY <table> 到文件/程序/客户端
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/commands/copyto.c
 *
 *-------------------------------------------------------------------------
 */
#include "postgres.h"

#include <ctype.h>
#include <unistd.h>
#include <sys/stat.h>

#include "access/heapam.h"
#include "access/htup_details.h"
#include "access/tableam.h"
#include "access/xact.h"
#include "access/xlog.h"
#include "commands/copy.h"
#include "commands/progress.h"
#include "executor/execdesc.h"
#include "executor/executor.h"
#include "executor/tuptable.h"
#include "libpq/libpq.h"
#include "libpq/pqformat.h"
#include "mb/pg_wchar.h"
#include "miscadmin.h"
#include "optimizer/optimizer.h"
#include "pgstat.h"
#include "rewrite/rewriteHandler.h"
#include "storage/fd.h"
#include "tcop/tcopprot.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/partcache.h"
#include "utils/rel.h"
#include "utils/snapmgr.h"

/*
 * 代表我们需要担心的不同目标情况
 * 在底层
 */
typedef enum CopyDest
{
	COPY_FILE,					/* 到文件（或管道程序） */
	COPY_FRONTEND,				/* 到前端 */
} CopyDest;

/*
 * 这个结构包含了在 COPY TO 操作中使用的所有状态变量。
 *
 * 多字节编码：所有支持的客户端编码通过将第一个字节的高位设置为1来编码多字节字符。字符的后续字节可以将高位保持为0。当在这样的编码中扫描数据以查找与单字节（即 ASCII）字符的匹配时，我们必须使用完整的 pg_encoding_mblen() 机制来跳过多字节字符，否则我们可能会找到一个假匹配，导致解析到一个尾随字节。在支持的服务器编码中，不会有假匹配的可能性，进行无用的尾随字节比较通常比调用 pg_encoding_mblen() 跳过它们更快。encoding_embeds_ascii 为真，表示我们不得不使用更复杂的方法来处理。
 */
typedef struct CopyToStateData
{
	/* 低级状态数据 */
	CopyDest	copy_dest;		/* 复制源/目标的类型 */
	FILE	   *copy_file;		/* 如果 copy_dest == COPY_FILE 使用 */
	StringInfo	fe_msgbuf;		/* 在 COPY TO 期间用于所有目标 */

	int			file_encoding;	/* 文件或远程端的字符编码 */
	bool		need_transcoding;	/* 文件编码与服务器不同？ */
	bool		encoding_embeds_ascii;	/* ASCII 可以是非首字节？ */

	/* COPY 命令的参数 */
	Relation	rel;			/* 要复制的关系 */
	QueryDesc  *queryDesc;		/* 要复制的可执行查询 */
	List	   *attnumlist;		/* 要复制的 attnums 的整数列表 */
	char	   *filename;		/* 文件名，或 NULL 表示 STDOUT */
	bool		is_program;		/* 'filename' 是一个程序以 popen 吗？ */

	CopyFormatOptions opts;
	Node	   *whereClause;	/* WHERE 条件（或 NULL） */

	/*
	 * 工作状态
	 */
	MemoryContext copycontext;	/* 每个复制执行上下文 */

	FmgrInfo   *out_functions;	/* 输出函数的查找信息 */
	MemoryContext rowcontext;	/* 每行评估上下文 */
	uint64		bytes_processed;	/* 到目前为止处理的字节数 */
} CopyToStateData;

/* COPY（查询）目标接收器 */
typedef struct
{
	DestReceiver pub;			/* 公共已知的函数指针 */
	CopyToState cstate;			/* 命令的 CopyToStateData */
	uint64		processed;		/* 处理的元组数量 */
} DR_copy;

/* 注意：在 copyfromparse.c 中有一个副本 */
static const char BinarySignature[11] = "PGCOPY\n\377\r\n\0";


/* 非导出函数原型 */
static void fc_EndCopy(CopyToState fc_cstate);
static void fc_ClosePipeToProgram(CopyToState fc_cstate);
static void fc_CopyOneRowTo(CopyToState fc_cstate, TupleTableSlot *fc_slot);
static void fc_CopyAttributeOutText(CopyToState fc_cstate, const char *fc_string);
static void fc_CopyAttributeOutCSV(CopyToState fc_cstate, const char *fc_string,
								bool fc_use_quote, bool fc_single_attr);

/* 低级通信函数 */
static void fc_SendCopyBegin(CopyToState fc_cstate);
static void fc_SendCopyEnd(CopyToState fc_cstate);
static void fc_CopySendData(CopyToState fc_cstate, const void *fc_databuf, int fc_datasize);
static void fc_CopySendString(CopyToState fc_cstate, const char *fc_str);
static void fc_CopySendChar(CopyToState fc_cstate, char fc_c);
static void fc_CopySendEndOfRow(CopyToState fc_cstate);
static void fc_CopySendInt32(CopyToState fc_cstate, int32 fc_val);
static void fc_CopySendInt16(CopyToState fc_cstate, int16 fc_val);


/*
 * 发送复制开始/停止消息以用于前端复制。这些在过去的协议重设计中有所更改。
 */
static void fc_SendCopyBegin(CopyToState fc_cstate)
{
	StringInfoData fc_buf;
	int			fc_natts = list_length(fc_cstate->attnumlist);
	int16		fc_format = (fc_cstate->opts.binary ? 1 : 0);
	int			fc_i;

	pq_beginmessage(&fc_buf, 'H');
	pq_sendbyte(&fc_buf, fc_format);	/* 整体格式 */
	pq_sendint16(&fc_buf, fc_natts);
	for (fc_i = 0; fc_i < fc_natts; fc_i++)
		pq_sendint16(&fc_buf, fc_format); /* 每列格式 */
	pq_endmessage(&fc_buf);
	fc_cstate->copy_dest = COPY_FRONTEND;
}

static void fc_SendCopyEnd(CopyToState fc_cstate)
{
	/* 不应该有任何未发送的数据 */
	Assert(fc_cstate->fe_msgbuf->len == 0);
	/* 发送复制完成消息 */
	pq_putemptymessage('c');
}

/*----------
 * CopySendData 将输出数据发送到目标（文件或前端）
 * CopySendString 对空终止字符串执行相同操作
 * CopySendChar 对单个字符执行相同操作
 * CopySendEndOfRow 在每个数据行结束时执行适当的操作
 *	（数据实际上不会被冲刷，除非通过 CopySendEndOfRow）
 *
 * 注意：这些函数不应用数据转换
 *----------
 */
static void fc_CopySendData(CopyToState fc_cstate, const void *fc_databuf, int fc_datasize)
{
	appendBinaryStringInfo(fc_cstate->fe_msgbuf, fc_databuf, fc_datasize);
}

static void fc_CopySendString(CopyToState fc_cstate, const char *fc_str)
{
	appendBinaryStringInfo(fc_cstate->fe_msgbuf, fc_str, strlen(fc_str));
}

static void fc_CopySendChar(CopyToState fc_cstate, char fc_c)
{
	appendStringInfoCharMacro(fc_cstate->fe_msgbuf, fc_c);
}

static void fc_CopySendEndOfRow(CopyToState fc_cstate)
{
	StringInfo	fc_fe_msgbuf = fc_cstate->fe_msgbuf;

	switch (fc_cstate->copy_dest)
	{
		case COPY_FILE:
			if (!fc_cstate->opts.binary)
			{
				/* 默认行结束符取决于平台 */
#ifndef WIN32
				fc_CopySendChar(fc_cstate, '\n');
#else
				CopySendString(cstate, "\r\n");
#endif
			}

			if (fwrite(fc_fe_msgbuf->data, fc_fe_msgbuf->len, 1,
					   fc_cstate->copy_file) != 1 ||
				ferror(fc_cstate->copy_file))
			{
				if (fc_cstate->is_program)
				{
					if (errno == EPIPE)
					{
						/*
						 * 管道将在事务结束时自动关闭，但我们可能会从子进程的退出代码中获得比仅仅“断开的管道”更好的错误消息
						 */
						fc_ClosePipeToProgram(fc_cstate);

						/*
 * 如果 ClosePipeToProgram() 没有抛出错误，
 * 程序正常终止，但首先关闭管道。恢复 errno，并抛出错误。
 */
						errno = EPIPE;
					}
					ereport(ERROR,
							(errcode_for_file_access(),
							 errmsg("could not write to COPY program: %m")));
				}
				else
					ereport(ERROR,
							(errcode_for_file_access(),
							 errmsg("could not write to COPY file: %m")));
			}
			break;
		case COPY_FRONTEND:
			/* FE/BE 协议在所有平台上使用 \n 作为换行符 */
			if (!fc_cstate->opts.binary)
				fc_CopySendChar(fc_cstate, '\n');

			/* 将累积的行作为一个 CopyData 消息转储 */
			(void) pq_putmessage('d', fc_fe_msgbuf->data, fc_fe_msgbuf->len);
			break;
	}

	/* 更新进度 */
	fc_cstate->bytes_processed += fc_fe_msgbuf->len;
	pgstat_progress_update_param(PROGRESS_COPY_BYTES_PROCESSED, fc_cstate->bytes_processed);

	resetStringInfo(fc_fe_msgbuf);
}

/*
 * 这些函数确实应用了一些数据转换
 */

/*
 * CopySendInt32 以网络字节顺序发送一个 int32
 */
static inline void fc_CopySendInt32(CopyToState fc_cstate, int32 fc_val)
{
	uint32		fc_buf;

	fc_buf = pg_hton32((uint32) fc_val);
	fc_CopySendData(fc_cstate, &fc_buf, sizeof(fc_buf));
}

/*
 * CopySendInt16 以网络字节顺序发送一个 int16
 */
static inline void fc_CopySendInt16(CopyToState fc_cstate, int16 fc_val)
{
	uint16		fc_buf;

	fc_buf = pg_hton16((uint16) fc_val);
	fc_CopySendData(fc_cstate, &fc_buf, sizeof(fc_buf));
}

/*
 * 关闭与外部程序的管道，检查 pclose() 返回代码。
 */
static void fc_ClosePipeToProgram(CopyToState fc_cstate)
{
	int			fc_pclose_rc;

	Assert(fc_cstate->is_program);

	fc_pclose_rc = ClosePipeStream(fc_cstate->copy_file);
	if (fc_pclose_rc == -1)
		ereport(ERROR,
				(errcode_for_file_access(),
				 errmsg("could not close pipe to external command: %m")));
	else if (fc_pclose_rc != 0)
	{
		ereport(ERROR,
				(errcode(ERRCODE_EXTERNAL_ROUTINE_EXCEPTION),
				 errmsg("program \"%s\" failed",
						fc_cstate->filename),
				 errdetail_internal("%s", wait_result_to_str(fc_pclose_rc))));
	}
}

/*
 * 释放在 cstate 中为 COPY TO/FROM 分配的资源。
 */
static void fc_EndCopy(CopyToState fc_cstate)
{
	if (fc_cstate->is_program)
	{
		fc_ClosePipeToProgram(fc_cstate);
	}
	else
	{
		if (fc_cstate->filename != NULL && FreeFile(fc_cstate->copy_file))
			ereport(ERROR,
					(errcode_for_file_access(),
					 errmsg("could not close file \"%s\": %m",
							fc_cstate->filename)));
	}

	pgstat_progress_end_command();

	MemoryContextDelete(fc_cstate->copycontext);
	pfree(fc_cstate);
}

/*
 * 设置 CopyToState 从表或查询中读取元组以进行 COPY TO。
 */
CopyToState
BeginCopyTo(ParseState *fc_pstate,
			Relation fc_rel,
			RawStmt *fc_raw_query,
			Oid fc_queryRelId,
			const char *fc_filename,
			bool fc_is_program,
			List *fc_attnamelist,
			List *fc_options)
{
	CopyToState fc_cstate;
	bool		fc_pipe = (fc_filename == NULL);
	TupleDesc	fc_tupDesc;
	int			fc_num_phys_attrs;
	MemoryContext fc_oldcontext;
	const int	fc_progress_cols[] = {
		PROGRESS_COPY_COMMAND,
		PROGRESS_COPY_TYPE
	};
	int64		fc_progress_vals[] = {
		PROGRESS_COPY_COMMAND_TO,
		0
	};

	if (fc_rel != NULL && fc_rel->rd_rel->relkind != RELKIND_RELATION)
	{
		if (fc_rel->rd_rel->relkind == RELKIND_VIEW)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot copy from view \"%s\"",
							RelationGetRelationName(fc_rel)),
					 errhint("Try the COPY (SELECT ...) TO variant.")));
		else if (fc_rel->rd_rel->relkind == RELKIND_MATVIEW)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot copy from materialized view \"%s\"",
							RelationGetRelationName(fc_rel)),
					 errhint("Try the COPY (SELECT ...) TO variant.")));
		else if (fc_rel->rd_rel->relkind == RELKIND_FOREIGN_TABLE)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot copy from foreign table \"%s\"",
							RelationGetRelationName(fc_rel)),
					 errhint("Try the COPY (SELECT ...) TO variant.")));
		else if (fc_rel->rd_rel->relkind == RELKIND_SEQUENCE)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot copy from sequence \"%s\"",
							RelationGetRelationName(fc_rel))));
		else if (fc_rel->rd_rel->relkind == RELKIND_PARTITIONED_TABLE)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot copy from partitioned table \"%s\"",
							RelationGetRelationName(fc_rel)),
					 errhint("Try the COPY (SELECT ...) TO variant.")));
		else
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("cannot copy from non-table relation \"%s\"",
							RelationGetRelationName(fc_rel))));
	}


	/* 分配工作空间并将所有字段置为零 */
	fc_cstate = (CopyToStateData *) palloc0(sizeof(CopyToStateData));

	/*
	 * 我们在一个新的内存上下文中分配 cstate 使用的所有内容。这
	 * 避免在查询中重复使用 COPY 时的内存泄漏。
	 */
	fc_cstate->copycontext = AllocSetContextCreate(CurrentMemoryContext,
												"COPY",
												ALLOCSET_DEFAULT_SIZES);

	fc_oldcontext = MemoryContextSwitchTo(fc_cstate->copycontext);

	/* 从语句节点树中提取选项 */
	ProcessCopyOptions(fc_pstate, &fc_cstate->opts, false /* is_from */ , fc_options);

	/* 处理源/目标关系或查询 */
	if (fc_rel)
	{
		Assert(!fc_raw_query);

		fc_cstate->rel = fc_rel;

		fc_tupDesc = RelationGetDescr(fc_cstate->rel);
	}
	else
	{
		List	   *fc_rewritten;
		Query	   *fc_query;
		PlannedStmt *fc_plan;
		DestReceiver *fc_dest;

		fc_cstate->rel = NULL;

		/*
		 * 运行解析分析和重写。 注意，这也在源表上获取了足够的
		 * 锁。
		 */
		fc_rewritten = pg_analyze_and_rewrite_fixedparams(fc_raw_query,
													   fc_pstate->p_sourcetext, NULL, 0,
													   NULL);

		/* 检查我们是否得到了可以使用的内容 */
		if (fc_rewritten == NIL)
		{
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("DO INSTEAD NOTHING rules are not supported for COPY")));
		}
		else if (list_length(fc_rewritten) > 1)
		{
			ListCell   *fc_lc;

			/* 检查查询以确定发布哪个错误消息 */
			foreach(fc_lc, fc_rewritten)
			{
				Query	   *fc_q = lfirst_node(Query, fc_lc);

				if (fc_q->querySource == QSRC_QUAL_INSTEAD_RULE)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("conditional DO INSTEAD rules are not supported for COPY")));
				if (fc_q->querySource == QSRC_NON_INSTEAD_RULE)
					ereport(ERROR,
							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
							 errmsg("DO ALSO rules are not supported for COPY")));
			}

			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("multi-statement DO INSTEAD rules are not supported for COPY")));
		}

		fc_query = linitial_node(Query, fc_rewritten);

		/* 语法允许 SELECT INTO，但我们不支持它 */
		if (fc_query->utilityStmt != NULL &&
			IsA(fc_query->utilityStmt, CreateTableAsStmt))
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("COPY (SELECT INTO) is not supported")));

		/* 我们看到的唯一其他实用命令是 NOTIFY */
		if (fc_query->utilityStmt != NULL)
			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("COPY query must not be a utility command")));

		/*
		 * 同样，语法并未强制要求存在 RETURNING
		 * 从句，但在这里是必需的。
		 */
		if (fc_query->commandType != CMD_SELECT &&
			fc_query->returningList == NIL)
		{
			Assert(fc_query->commandType == CMD_INSERT ||
				   fc_query->commandType == CMD_UPDATE ||
				   fc_query->commandType == CMD_DELETE);

			ereport(ERROR,
					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
					 errmsg("COPY query must have a RETURNING clause")));
		}

		/* 计划查询 */
		fc_plan = pg_plan_query(fc_query, fc_pstate->p_sourcetext,
							 CURSOR_OPT_PARALLEL_OK, NULL);

		/*
		 * 在行级安全性以及用户使用 "COPY relation TO" 的情况下，
		 * 我们必须将 "COPY relation TO" 转换为基于查询的 COPY（例如：
		 * "COPY (SELECT * FROM ONLY relation) TO"），以允许重写器
		 * 添加任何 RLS 从句。
		 *
		 * 当这种情况发生时，我们将原本找到的关系的 relid 传递
		 * 进来（我们已锁定该关系）。由于规划器将再次查找
		 * 该关系，因此我们在这里进行二次检查，以确保它找到的
		 * 是我们锁定的那个。
		 */
		if (fc_queryRelId != InvalidOid)
		{
			/*
			 * 请注意，涉及 RLS 时可能会有多个关系，
			 * 虽然我们需要的关系几乎肯定是第一个，但在规划器中
			 * 我们并不保证这一点，因此检查整个
			 * 列表并确保找到原始关系。
			 */
			if (!list_member_oid(fc_plan->relationOids, fc_queryRelId))
				ereport(ERROR,
						(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
						 errmsg("relation referenced by COPY statement has changed")));
		}

		/*
		 * 使用带有更新命令 ID 的快照以确保此查询可以看到
		 * 任何之前执行的查询的结果。
		 */
		PushCopiedSnapshot(GetActiveSnapshot());
		UpdateActiveSnapshotCommandId();

		/* 为 COPY OUT 创建目标接收器 */
		fc_dest = CreateDestReceiver(DestCopyOut);
		((DR_copy *) fc_dest)->cstate = fc_cstate;

		/* 创建一个请求没有输出的 QueryDesc */
		fc_cstate->queryDesc = CreateQueryDesc(fc_plan, fc_pstate->p_sourcetext,
											GetActiveSnapshot(),
											InvalidSnapshot,
											fc_dest, NULL, NULL, 0);

		/*
		 * 调用 ExecutorStart 来准备执行计划。
		 *
		 * ExecutorStart 为我们计算结果 tupdesc
		 */
		ExecutorStart(fc_cstate->queryDesc, 0);

		fc_tupDesc = fc_cstate->queryDesc->tupDesc;
	}

	/* 生成或转换待处理的属性列表 */
	fc_cstate->attnumlist = CopyGetAttnums(fc_tupDesc, fc_cstate->rel, fc_attnamelist);

	fc_num_phys_attrs = fc_tupDesc->natts;

	/* 将 FORCE_QUOTE 名称列表转换为每列标志，检查有效性 */
	fc_cstate->opts.force_quote_flags = (bool *) palloc0(fc_num_phys_attrs * sizeof(bool));
	if (fc_cstate->opts.force_quote_all)
	{
		int			fc_i;

		for (fc_i = 0; fc_i < fc_num_phys_attrs; fc_i++)
			fc_cstate->opts.force_quote_flags[fc_i] = true;
	}
	else if (fc_cstate->opts.force_quote)
	{
		List	   *fc_attnums;
		ListCell   *fc_cur;

		fc_attnums = CopyGetAttnums(fc_tupDesc, fc_cstate->rel, fc_cstate->opts.force_quote);

		foreach(fc_cur, fc_attnums)
		{
			int			fc_attnum = lfirst_int(fc_cur);
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupDesc, fc_attnum - 1);

			if (!list_member_int(fc_cstate->attnumlist, fc_attnum))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
						 errmsg("FORCE_QUOTE column \"%s\" not referenced by COPY",
								NameStr(fc_attr->attname))));
			fc_cstate->opts.force_quote_flags[fc_attnum - 1] = true;
		}
	}

	/* 将 FORCE_NOT_NULL 名称列表转换为逐列标志，检查有效性 */
	fc_cstate->opts.force_notnull_flags = (bool *) palloc0(fc_num_phys_attrs * sizeof(bool));
	if (fc_cstate->opts.force_notnull)
	{
		List	   *fc_attnums;
		ListCell   *fc_cur;

		fc_attnums = CopyGetAttnums(fc_tupDesc, fc_cstate->rel, fc_cstate->opts.force_notnull);

		foreach(fc_cur, fc_attnums)
		{
			int			fc_attnum = lfirst_int(fc_cur);
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupDesc, fc_attnum - 1);

			if (!list_member_int(fc_cstate->attnumlist, fc_attnum))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
						 errmsg("FORCE_NOT_NULL column \"%s\" not referenced by COPY",
								NameStr(fc_attr->attname))));
			fc_cstate->opts.force_notnull_flags[fc_attnum - 1] = true;
		}
	}

	/* 将 FORCE_NULL 名称列表转换为逐列标志，检查有效性 */
	fc_cstate->opts.force_null_flags = (bool *) palloc0(fc_num_phys_attrs * sizeof(bool));
	if (fc_cstate->opts.force_null)
	{
		List	   *fc_attnums;
		ListCell   *fc_cur;

		fc_attnums = CopyGetAttnums(fc_tupDesc, fc_cstate->rel, fc_cstate->opts.force_null);

		foreach(fc_cur, fc_attnums)
		{
			int			fc_attnum = lfirst_int(fc_cur);
			Form_pg_attribute fc_attr = TupleDescAttr(fc_tupDesc, fc_attnum - 1);

			if (!list_member_int(fc_cstate->attnumlist, fc_attnum))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
						 errmsg("FORCE_NULL column \"%s\" not referenced by COPY",
								NameStr(fc_attr->attname))));
			fc_cstate->opts.force_null_flags[fc_attnum - 1] = true;
		}
	}

	/* 当未指定 ENCODING 选项时使用客户端编码。 */
	if (fc_cstate->opts.file_encoding < 0)
		fc_cstate->file_encoding = pg_get_client_encoding();
	else
		fc_cstate->file_encoding = fc_cstate->opts.file_encoding;

	/*
	 * 设置编码转换信息。即使文件和服务器编码相同，
	 * 我们也必须应用 pg_any_to_server() 来验证多字节编码中的数据。
	 */
	fc_cstate->need_transcoding =
		(fc_cstate->file_encoding != GetDatabaseEncoding() ||
		 pg_database_encoding_max_length() > 1);
	/* 请参阅上述多字节编码注释 */
	fc_cstate->encoding_embeds_ascii = PG_ENCODING_IS_CLIENT_ONLY(fc_cstate->file_encoding);

	fc_cstate->copy_dest = COPY_FILE;	/* 默认 */

	if (fc_pipe)
	{
		fc_progress_vals[1] = PROGRESS_COPY_TYPE_PIPE;

		Assert(!fc_is_program);	/* 语法不允许这样做 */
		if (whereToSendOutput != DestRemote)
			fc_cstate->copy_file = stdout;
	}
	else
	{
		fc_cstate->filename = pstrdup(fc_filename);
		fc_cstate->is_program = fc_is_program;

		if (fc_is_program)
		{
			fc_progress_vals[1] = PROGRESS_COPY_TYPE_PROGRAM;
			fc_cstate->copy_file = OpenPipeStream(fc_cstate->filename, PG_BINARY_W);
			if (fc_cstate->copy_file == NULL)
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not execute command \"%s\": %m",
								fc_cstate->filename)));
		}
		else
		{
			mode_t		fc_oumask; /* 现有的 umask 值 */
			struct stat fc_st;

			fc_progress_vals[1] = PROGRESS_COPY_TYPE_FILE;

			/*
			 * 防止写入相对路径... 轻易会因覆盖数据库文件而自食其果...
			 */
			if (!is_absolute_path(fc_filename))
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_NAME),
						 errmsg("relative path not allowed for COPY to file")));

			fc_oumask = umask(S_IWGRP | S_IWOTH);
			PG_TRY();
			{
				fc_cstate->copy_file = AllocateFile(fc_cstate->filename, PG_BINARY_W);
			}
			PG_FINALLY();
			{
				umask(fc_oumask);
			}
			PG_END_TRY();
			if (fc_cstate->copy_file == NULL)
			{
				/* 复制errno，因为ereport子函数可能会更改它 */
				int			fc_save_errno = errno;

				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not open file \"%s\" for writing: %m",
								fc_cstate->filename),
						 (fc_save_errno == ENOENT || fc_save_errno == EACCES) ?
						 errhint("COPY TO instructs the PostgreSQL server process to write a file. "
								 "You may want a client-side facility such as psql's \\copy.") : 0));
			}

			if (fstat(fileno(fc_cstate->copy_file), &fc_st))
				ereport(ERROR,
						(errcode_for_file_access(),
						 errmsg("could not stat file \"%s\": %m",
								fc_cstate->filename)));

			if (S_ISDIR(fc_st.st_mode))
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("\"%s\" is a directory", fc_cstate->filename)));
		}
	}

	/* 初始化进度 */
	pgstat_progress_start_command(PROGRESS_COMMAND_COPY,
								  fc_cstate->rel ? RelationGetRelid(fc_cstate->rel) : InvalidOid);
	pgstat_progress_update_multi_param(2, fc_progress_cols, fc_progress_vals);

	fc_cstate->bytes_processed = 0;

	MemoryContextSwitchTo(fc_oldcontext);

	return fc_cstate;
}

/*
 * 清理存储并释放 COPY TO 的资源。
 */
void EndCopyTo(CopyToState fc_cstate)
{
	if (fc_cstate->queryDesc != NULL)
	{
		/* 关闭查询并释放资源。 */
		ExecutorFinish(fc_cstate->queryDesc);
		ExecutorEnd(fc_cstate->queryDesc);
		FreeQueryDesc(fc_cstate->queryDesc);
		PopActiveSnapshot();
	}

	/* 清理存储 */
	fc_EndCopy(fc_cstate);
}

/*
 * 从关系或查询复制到文件。
 */
uint64 DoCopyTo(CopyToState fc_cstate)
{
	bool		fc_pipe = (fc_cstate->filename == NULL);
	bool		fc_fe_copy = (fc_pipe && whereToSendOutput == DestRemote);
	TupleDesc	fc_tupDesc;
	int			fc_num_phys_attrs;
	ListCell   *fc_cur;
	uint64		fc_processed;

	if (fc_fe_copy)
		fc_SendCopyBegin(fc_cstate);

	if (fc_cstate->rel)
		fc_tupDesc = RelationGetDescr(fc_cstate->rel);
	else
		fc_tupDesc = fc_cstate->queryDesc->tupDesc;
	fc_num_phys_attrs = fc_tupDesc->natts;
	fc_cstate->opts.null_print_client = fc_cstate->opts.null_print;	/* 默认 */

	/* 我们使用 fe_msgbuf 作为逐行缓冲区，无论 copy_dest 如何 */
	fc_cstate->fe_msgbuf = makeStringInfo();

	/* 获取我们需要处理的列的信息。 */
	fc_cstate->out_functions = (FmgrInfo *) palloc(fc_num_phys_attrs * sizeof(FmgrInfo));
	foreach(fc_cur, fc_cstate->attnumlist)
	{
		int			fc_attnum = lfirst_int(fc_cur);
		Oid			fc_out_func_oid;
		bool		fc_isvarlena;
		Form_pg_attribute fc_attr = TupleDescAttr(fc_tupDesc, fc_attnum - 1);

		if (fc_cstate->opts.binary)
			getTypeBinaryOutputInfo(fc_attr->atttypid,
									&fc_out_func_oid,
									&fc_isvarlena);
		else
			getTypeOutputInfo(fc_attr->atttypid,
							  &fc_out_func_oid,
							  &fc_isvarlena);
		fmgr_info(fc_out_func_oid, &fc_cstate->out_functions[fc_attnum - 1]);
	}

	/*
	 * 创建一个临时内存上下文，我们可以每行重置一次以
	 * 回收已分配的内存。这可以避免数据类型输出例程内的泄漏问题，
	 * 而且应该比逐个 pfree 快。(我们不需要整整个 econtext，就像 CopyFrom 那样。)
	 */
	fc_cstate->rowcontext = AllocSetContextCreate(CurrentMemoryContext,
											   "COPY TO",
											   ALLOCSET_DEFAULT_SIZES);

	if (fc_cstate->opts.binary)
	{
		/* 为二进制复制生成头部 */
		int32		fc_tmp;

		/* 签名 */
		fc_CopySendData(fc_cstate, BinarySignature, 11);
		/* 标志字段 */
		fc_tmp = 0;
		fc_CopySendInt32(fc_cstate, fc_tmp);
		/* 无头部扩展 */
		fc_tmp = 0;
		fc_CopySendInt32(fc_cstate, fc_tmp);
	}
	else
	{
		/*
		 * 对于非二进制复制，我们需要将 null_print 转换为文件
		 * 编码，因为它将直接通过 CopySendString 发送。
		 */
		if (fc_cstate->need_transcoding)
			fc_cstate->opts.null_print_client = pg_server_to_any(fc_cstate->opts.null_print,
															  fc_cstate->opts.null_print_len,
															  fc_cstate->file_encoding);

		/* 如果请求了头部，则发送该行 */
		if (fc_cstate->opts.header_line)
		{
			bool		fc_hdr_delim = false;

			foreach(fc_cur, fc_cstate->attnumlist)
			{
				int			fc_attnum = lfirst_int(fc_cur);
				char	   *fc_colname;

				if (fc_hdr_delim)
					fc_CopySendChar(fc_cstate, fc_cstate->opts.delim[0]);
				fc_hdr_delim = true;

				fc_colname = NameStr(TupleDescAttr(fc_tupDesc, fc_attnum - 1)->attname);

				if (fc_cstate->opts.csv_mode)
					fc_CopyAttributeOutCSV(fc_cstate, fc_colname, false,
										list_length(fc_cstate->attnumlist) == 1);
				else
					fc_CopyAttributeOutText(fc_cstate, fc_colname);
			}

			fc_CopySendEndOfRow(fc_cstate);
		}
	}

	if (fc_cstate->rel)
	{
		TupleTableSlot *fc_slot;
		TableScanDesc fc_scandesc;

		fc_scandesc = table_beginscan(fc_cstate->rel, GetActiveSnapshot(), 0, NULL);
		fc_slot = table_slot_create(fc_cstate->rel, NULL);

		fc_processed = 0;
		while (table_scan_getnextslot(fc_scandesc, ForwardScanDirection, fc_slot))
		{
			CHECK_FOR_INTERRUPTS();

			/* 解构元组... */
			slot_getallattrs(fc_slot);

			/* 格式化并发送数据 */
			fc_CopyOneRowTo(fc_cstate, fc_slot);

			/*
			 * 增加处理的元组数量，并报告进度。
			 */
			pgstat_progress_update_param(PROGRESS_COPY_TUPLES_PROCESSED,
										 ++fc_processed);
		}

		ExecDropSingleTupleTableSlot(fc_slot);
		table_endscan(fc_scandesc);
	}
	else
	{
		/* 运行计划 --- 目标接收者将发送元组 */
		ExecutorRun(fc_cstate->queryDesc, ForwardScanDirection, 0L, true);
		fc_processed = ((DR_copy *) fc_cstate->queryDesc->dest)->processed;
	}

	if (fc_cstate->opts.binary)
	{
		/* 为二进制复制生成尾部 */
		fc_CopySendInt16(fc_cstate, -1);
		/* 需要刷新尾部 */
		fc_CopySendEndOfRow(fc_cstate);
	}

	MemoryContextDelete(fc_cstate->rowcontext);

	if (fc_fe_copy)
		fc_SendCopyEnd(fc_cstate);

	return fc_processed;
}

/*
 * 在 DoCopyTo() 中发出一行。
 */
static void fc_CopyOneRowTo(CopyToState fc_cstate, TupleTableSlot *fc_slot)
{
	bool		fc_need_delim = false;
	FmgrInfo   *fc_out_functions = fc_cstate->out_functions;
	MemoryContext fc_oldcontext;
	ListCell   *fc_cur;
	char	   *fc_string;

	MemoryContextReset(fc_cstate->rowcontext);
	fc_oldcontext = MemoryContextSwitchTo(fc_cstate->rowcontext);

	if (fc_cstate->opts.binary)
	{
		/* 二进制逐元组头部 */
		fc_CopySendInt16(fc_cstate, list_length(fc_cstate->attnumlist));
	}

	/* 确保元组已完全解构 */
	slot_getallattrs(fc_slot);

	foreach(fc_cur, fc_cstate->attnumlist)
	{
		int			fc_attnum = lfirst_int(fc_cur);
		Datum		fc_value = fc_slot->tts_values[fc_attnum - 1];
		bool		fc_isnull = fc_slot->tts_isnull[fc_attnum - 1];

		if (!fc_cstate->opts.binary)
		{
			if (fc_need_delim)
				fc_CopySendChar(fc_cstate, fc_cstate->opts.delim[0]);
			fc_need_delim = true;
		}

		if (fc_isnull)
		{
			if (!fc_cstate->opts.binary)
				fc_CopySendString(fc_cstate, fc_cstate->opts.null_print_client);
			else
				fc_CopySendInt32(fc_cstate, -1);
		}
		else
		{
			if (!fc_cstate->opts.binary)
			{
				fc_string = OutputFunctionCall(&fc_out_functions[fc_attnum - 1],
											fc_value);
				if (fc_cstate->opts.csv_mode)
					fc_CopyAttributeOutCSV(fc_cstate, fc_string,
										fc_cstate->opts.force_quote_flags[fc_attnum - 1],
										list_length(fc_cstate->attnumlist) == 1);
				else
					fc_CopyAttributeOutText(fc_cstate, fc_string);
			}
			else
			{
				bytea	   *fc_outputbytes;

				fc_outputbytes = SendFunctionCall(&fc_out_functions[fc_attnum - 1],
											   fc_value);
				fc_CopySendInt32(fc_cstate, VARSIZE(fc_outputbytes) - VARHDRSZ);
				fc_CopySendData(fc_cstate, VARDATA(fc_outputbytes),
							 VARSIZE(fc_outputbytes) - VARHDRSZ);
			}
		}
	}

	fc_CopySendEndOfRow(fc_cstate);

	MemoryContextSwitchTo(fc_oldcontext);
}

/*
 * 发送一个属性的文本表示形式，带有转换和转义
 */
#define DUMPSOFAR() \
	do { \
		if (fc_ptr > fc_start) \
			fc_CopySendData(fc_cstate, fc_start, fc_ptr - fc_start); \
	} while (0)

static void fc_CopyAttributeOutText(CopyToState fc_cstate, const char *fc_string)
{
	const char *fc_ptr;
	const char *fc_start;
	char		fc_c;
	char		fc_delimc = fc_cstate->opts.delim[0];

	if (fc_cstate->need_transcoding)
		fc_ptr = pg_server_to_any(fc_string, strlen(fc_string), fc_cstate->file_encoding);
	else
		fc_ptr = fc_string;

	/*
	 * 我们必须搜索字符串中的控制字符
	 * 和分隔符字符的实例。尽管在大多数情况下，
	 * 这些是比较少见的。为了避免每次字符调用 CopySendData 时
	 * 产生的开销，我们在一次调用中转储所有转义字符之间的字符。
	 * 循环的不变性是从“start”到“ptr”的数据
	 * 可以被字面发送，但还没发送。
	 *
	 * 当编码安全时，我们可以跳过 pg_encoding_mblen() 的开销，因为
	 * 在有效的后端编码中，多字节字符的额外字节从不
	 * 看起来像 ASCII。这个循环的性能关键性足够高，
	 * 值得制作两个副本，以将 IS_HIGHBIT_SET() 测试排除在
	 * 正常的安全编码路径之外。
	 */
	if (fc_cstate->encoding_embeds_ascii)
	{
		fc_start = fc_ptr;
		while ((fc_c = *fc_ptr) != '\0')
		{
			if ((unsigned char) fc_c < (unsigned char) 0x20)
			{
				/*
				 * \r和\n必须转义，其他都是传统的。我们
				 * 更喜欢使用类似C的表示法来转储这些，而不是
				 * 反斜杠和字面字符，因为这使得
				 * 转储文件对微软式的数据
				 * 破坏更加具有抵御力。
				 */
				switch (fc_c)
				{
					case '\b':
						fc_c = 'b';
						break;
					case '\f':
						fc_c = 'f';
						break;
					case '\n':
						fc_c = 'n';
						break;
					case '\r':
						fc_c = 'r';
						break;
					case '\t':
						fc_c = 't';
						break;
					case '\v':
						fc_c = 'v';
						break;
					default:
						/* 如果是分隔符，必须转义 */
						if (fc_c == fc_delimc)
							break;
						/* 所有ASCII控制字符的长度为1 */
						fc_ptr++;
						continue;	/* 进入循环末尾 */
				}
				/* 如果我们到这里，就需要转换控制字符 */
				DUMPSOFAR();
				fc_CopySendChar(fc_cstate, '\\');
				fc_CopySendChar(fc_cstate, fc_c);
				fc_start = ++fc_ptr;	/* 不要在下一轮中包含字符 */
			}
			else if (fc_c == '\\' || fc_c == fc_delimc)
			{
				DUMPSOFAR();
				fc_CopySendChar(fc_cstate, '\\');
				fc_start = fc_ptr++;	/* 我们在下一轮中包含字符 */
			}
			else if (IS_HIGHBIT_SET(fc_c))
				fc_ptr += pg_encoding_mblen(fc_cstate->file_encoding, fc_ptr);
			else
				fc_ptr++;
		}
	}
	else
	{
		fc_start = fc_ptr;
		while ((fc_c = *fc_ptr) != '\0')
		{
			if ((unsigned char) fc_c < (unsigned char) 0x20)
			{
				/*
				 * \r和\n必须转义，其他都是传统的。我们
				 * 更喜欢使用类似C的表示法来转储这些，而不是
				 * 反斜杠和字面字符，因为这使得
				 * 转储文件对微软式的数据
				 * 破坏更加具有抵御力。
				 */
				switch (fc_c)
				{
					case '\b':
						fc_c = 'b';
						break;
					case '\f':
						fc_c = 'f';
						break;
					case '\n':
						fc_c = 'n';
						break;
					case '\r':
						fc_c = 'r';
						break;
					case '\t':
						fc_c = 't';
						break;
					case '\v':
						fc_c = 'v';
						break;
					default:
						/* 如果是分隔符，必须转义 */
						if (fc_c == fc_delimc)
							break;
						/* 所有ASCII控制字符的长度为1 */
						fc_ptr++;
						continue;	/* 进入循环末尾 */
				}
				/* 如果我们到这里，就需要转换控制字符 */
				DUMPSOFAR();
				fc_CopySendChar(fc_cstate, '\\');
				fc_CopySendChar(fc_cstate, fc_c);
				fc_start = ++fc_ptr;	/* 不要在下一轮中包含字符 */
			}
			else if (fc_c == '\\' || fc_c == fc_delimc)
			{
				DUMPSOFAR();
				fc_CopySendChar(fc_cstate, '\\');
				fc_start = fc_ptr++;	/* 我们在下一轮中包含字符 */
			}
			else
				fc_ptr++;
		}
	}

	DUMPSOFAR();
}

/*
 * 发送一个属性的文本表示，带有转换和
 * CSV风格的转义
 */
static void fc_CopyAttributeOutCSV(CopyToState fc_cstate, const char *fc_string,
					bool fc_use_quote, bool fc_single_attr)
{
	const char *fc_ptr;
	const char *fc_start;
	char		fc_c;
	char		fc_delimc = fc_cstate->opts.delim[0];
	char		fc_quotec = fc_cstate->opts.quote[0];
	char		fc_escapec = fc_cstate->opts.escape[0];

	/* 如果匹配null_print，强制引用（转换之前！） */
	if (!fc_use_quote && strcmp(fc_string, fc_cstate->opts.null_print) == 0)
		fc_use_quote = true;

	if (fc_cstate->need_transcoding)
		fc_ptr = pg_server_to_any(fc_string, strlen(fc_string), fc_cstate->file_encoding);
	else
		fc_ptr = fc_string;

	/*
	 * 进行初步处理，发现是否需要引用
	 */
	if (!fc_use_quote)
	{
		/*
		 * 因为'\.'可以是一个数据值，如果单独出现在一行中则引用它，
		 * 这样它就不会被解释为数据结束标记。
		 */
		if (fc_single_attr && strcmp(fc_ptr, "\\.") == 0)
			fc_use_quote = true;
		else
		{
			const char *fc_tptr = fc_ptr;

			while ((fc_c = *fc_tptr) != '\0')
			{
				if (fc_c == fc_delimc || fc_c == fc_quotec || fc_c == '\n' || fc_c == '\r')
				{
					fc_use_quote = true;
					break;
				}
				if (IS_HIGHBIT_SET(fc_c) && fc_cstate->encoding_embeds_ascii)
					fc_tptr += pg_encoding_mblen(fc_cstate->file_encoding, fc_tptr);
				else
					fc_tptr++;
			}
		}
	}

	if (fc_use_quote)
	{
		fc_CopySendChar(fc_cstate, fc_quotec);

		/*
		 * 我们采用与CopyAttributeOutText相同的优化策略
		 */
		fc_start = fc_ptr;
		while ((fc_c = *fc_ptr) != '\0')
		{
			if (fc_c == fc_quotec || fc_c == fc_escapec)
			{
				DUMPSOFAR();
				fc_CopySendChar(fc_cstate, fc_escapec);
				fc_start = fc_ptr;	/* 我们在下一轮中包含字符 */
			}
			if (IS_HIGHBIT_SET(fc_c) && fc_cstate->encoding_embeds_ascii)
				fc_ptr += pg_encoding_mblen(fc_cstate->file_encoding, fc_ptr);
			else
				fc_ptr++;
		}
		DUMPSOFAR();

		fc_CopySendChar(fc_cstate, fc_quotec);
	}
	else
	{
		/* 如果不需要引用，我们可以直接转储它 */
		fc_CopySendString(fc_cstate, fc_ptr);
	}
}

/*
 * copy_dest_startup --- 执行器启动
 */
static void fc_copy_dest_startup(DestReceiver *fc_self, int fc_operation, TupleDesc fc_typeinfo)
{
	/* 不执行操作 */
}

/*
 * copy_dest_receive --- 接收一个元组
 */
static bool fc_copy_dest_receive(TupleTableSlot *fc_slot, DestReceiver *fc_self)
{
	DR_copy    *fc_myState = (DR_copy *) fc_self;
	CopyToState fc_cstate = fc_myState->cstate;

	/* 发送数据 */
	fc_CopyOneRowTo(fc_cstate, fc_slot);

	/* 增加处理的元组数量，并报告进度 */
	pgstat_progress_update_param(PROGRESS_COPY_TUPLES_PROCESSED,
								 ++fc_myState->processed);

	return true;
}

/*
 * copy_dest_shutdown --- 执行器结束
 */
static void fc_copy_dest_shutdown(DestReceiver *fc_self)
{
	/* 不执行操作 */
}

/*
 * copy_dest_destroy --- 释放DestReceiver对象
 */
static void fc_copy_dest_destroy(DestReceiver *fc_self)
{
	pfree(fc_self);
}

/*
 * CreateCopyDestReceiver -- 创建合适的DestReceiver对象
 */
DestReceiver * CreateCopyDestReceiver(void)
{
	DR_copy    *fc_self = (DR_copy *) palloc(sizeof(DR_copy));

	fc_self->pub.receiveSlot = fc_copy_dest_receive;
	fc_self->pub.rStartup = fc_copy_dest_startup;
	fc_self->pub.rShutdown = fc_copy_dest_shutdown;
	fc_self->pub.rDestroy = fc_copy_dest_destroy;
	fc_self->pub.mydest = DestCopyOut;

	fc_self->cstate = NULL;		/* 稍后设置 */
	fc_self->processed = 0;

	return (DestReceiver *) fc_self;
}
